
#ifndef    TEST_HELP_LOGGER_HPP
#define    TEST_HELP_LOGGER_HPP

#include    <stdio.h>
#include    <sys/types.h>
#include    <time.h>
#include    <stdarg.h>

namespace help
{

class    LogRecord;
class    LogLevel;

///////////////////////////////////////////////////////////////////////

class    LogRecord
{
public    :
    LogRecord(const char* pszTimestamp, int iLevel, const char* pszMessage, const char* pszFileName = NULL, int iLineNo = 0)
        :    m_pszTimestamp(pszTimestamp), m_iLevel(iLevel), m_pszMessage(pszMessage), m_pszFileName(pszFileName), m_iLineNo(iLineNo)
    {}
            
public    :
    const char*    timestamp(void) const { return m_pszTimestamp; }
    int            level(void)     const { return m_iLevel; }
    const char*    message(void)   const { return m_pszMessage; }
    const char*    filename(void)  const { return m_pszFileName; }
    int            lineno(void)    const { return m_iLineNo; }
                
private    :
    const char*    m_pszTimestamp;
    int    m_iLevel;
    const char*    m_pszMessage;
    const char*    m_pszFileName;
    int    m_iLineNo;
};

class    LogLevel
{
public    :

    enum {
        ERROR = 1,
        INFO  = 2,
        DEBUG = 3,
        ALL = 4
    };
    
    static  const char*   name(int iLevel)
    {
        static    const char*    s_ppszName[] = {
            "UNKNOWN", "ERROR", "INFO", "DEBUG", "ALL",
        };    
        
        if ((iLevel < ERROR) || (iLevel >= ALL)) {
            return    s_ppszName[0];
        }
        else {
            return    s_ppszName[iLevel];
        }
    }
    
private    :
    LogLevel();
    
};

///////////////////////////////////////////////////////////////////////


class    Logger
{

public    :
    ~Logger()
    {
    }

public    :

    void    error(const char* pszFile, int iLineNo, const char* pszFormat, ... )
    {
        char        szTimestamp[32];
        char        szMessage[256];
        getTimeString(szTimestamp, sizeof(szTimestamp));
        
        va_list     args;
        va_start(args, pszFormat);
        int iLength = vsnprintf(szMessage, sizeof(szMessage) - 1, pszFormat, args);
        va_end(args);
        szMessage[iLength] = '\0';
        
        LogRecord    record(szTimestamp, LogLevel::ERROR, szMessage, pszFile, iLineNo);
        this->append(record);
    }
        
    void    error(const char* pszFormat, ... )
    {
        char        szTimestamp[32];
        char        szMessage[256];
        getTimeString(szTimestamp, sizeof(szTimestamp));
        
        va_list     args;
        va_start(args, pszFormat);
        int iLength = vsnprintf(szMessage, sizeof(szMessage) - 1, pszFormat, args);
        va_end(args);
        szMessage[iLength] = '\0';
        
        LogRecord    record(szTimestamp, LogLevel::ERROR, szMessage);
        this->append(record);
    }
        
    void    info(const char* pszFile, int iLineNo, const char* pszFormat, ... )
    {
        char        szTimestamp[32];
        char        szMessage[256];
        getTimeString(szTimestamp, sizeof(szTimestamp));
        
        va_list     args;
        va_start(args, pszFormat);
        int iLength = vsnprintf(szMessage, sizeof(szMessage) - 1, pszFormat, args);
        va_end(args);
        szMessage[iLength] = '\0';
        
        LogRecord    record(szTimestamp, LogLevel::INFO, szMessage, pszFile, iLineNo);
        this->append(record);
    }
        
    void    info(const char* pszFormat, ... )
    {
        char        szTimestamp[32];
        char        szMessage[256];
        getTimeString(szTimestamp, sizeof(szTimestamp));
        
        va_list     args;
        va_start(args, pszFormat);
        int iLength = vsnprintf(szMessage, sizeof(szMessage) - 1, pszFormat, args);
        va_end(args);
        szMessage[iLength] = '\0';
        
        LogRecord    record(szTimestamp, LogLevel::INFO, szMessage);
        this->append(record);
    }
        
    void    debug(const char* pszFile, int iLineNo, const char* pszFormat, ... )
    {
        char        szTimestamp[32];
        char        szMessage[256];
        getTimeString(szTimestamp, sizeof(szTimestamp));
        
        va_list     args;
        va_start(args, pszFormat);
        int iLength = vsnprintf(szMessage, sizeof(szMessage) - 1, pszFormat, args);
        va_end(args);
        szMessage[iLength] = '\0';
        
        LogRecord    record(szTimestamp, LogLevel::DEBUG, szMessage, pszFile, iLineNo);
        this->append(record);
    }
        
    void    debug(const char* pszFormat, ... )
    {
        char        szTimestamp[32];
        char        szMessage[256];
        getTimeString(szTimestamp, sizeof(szTimestamp));
        
        va_list     args;
        va_start(args, pszFormat);
        int iLength = vsnprintf(szMessage, sizeof(szMessage) - 1, pszFormat, args);
        va_end(args);
        szMessage[iLength] = '\0';
        
        LogRecord    record(szTimestamp, LogLevel::DEBUG, szMessage);
        this->append(record);
    }

private    :

    void    append( const LogRecord& record )
    {
        if ( LogLevel::ERROR == record.level() ) {
            if ( NULL == record.filename() ) {
                fprintf(m_pFileError, "%s [%s] %s\n", record.timestamp(), LogLevel::name(record.level()), record.message());
            } else {
                fprintf(m_pFileError, "%s %s:%d [%s] %s\n", record.timestamp(), record.filename(), record.lineno(), LogLevel::name(record.level()), record.message());
            }
        }
        else if ( LogLevel::INFO == record.level() ) {
            if ( NULL == record.filename() ) {
                fprintf(m_pFileInfo, "%s [%s] %s\n", record.timestamp(), LogLevel::name(record.level()), record.message());
            } else {
                fprintf(m_pFileInfo, "%s %s:%d [%s] %s\n", record.timestamp(), record.filename(), record.lineno(), LogLevel::name(record.level()), record.message());
            }
        }
        else if ( LogLevel::DEBUG == record.level() ) {
            if ( NULL == record.filename() ) {
                fprintf(m_pFileDebug, "%s [%s] %s\n", record.timestamp(), LogLevel::name(record.level()), record.message());
            } else {
                fprintf(m_pFileDebug, "%s %s:%d [%s] %s\n", record.timestamp(), record.filename(), record.lineno(), LogLevel::name(record.level()), record.message());
            }
        }
    }
            
public    :
    static    Logger*    s_pLogger;

    static  Logger&  getInstance(void)
    {
        if (NULL == s_pLogger) {
            s_pLogger = new Logger();
        }
        
        return    *s_pLogger;
    }
    
    static    int     getTimeString ( char* pszBuf, size_t iBufSize )
    {
        struct tm*  tmp;
        time_t  t;
        int     iReturn;
        
        pszBuf[0] = '\0';
        
        t = time (NULL);
        tmp = localtime( &t );
        if ( NULL == tmp ) {
            return  -1;
        }
        
        pszBuf[0] = '\0';
        iReturn = strftime(pszBuf, iBufSize, "%Y-%m-%d %H:%M:%S", tmp);
        if (iReturn > 0) {
            pszBuf[ iReturn ] = '\0';
        }
        
        return  0;
    }   

private    :
    Logger(const char* pszConfigure = NULL)
        :    m_pFileError(stderr), m_pFileInfo(stdout), m_pFileDebug(stdout)
    {
    }
    
private    :
    FILE*    m_pFileError;
    FILE*    m_pFileInfo;
    FILE*    m_pFileDebug;
};

Logger*    Logger::s_pLogger = NULL;

Logger&    getLogger(void)
{
    return    Logger::getInstance();
}

}    // namespace help

#endif
